#pragma once

#include <atomic>
#include <memory>
#include <mutex>

namespace good_cptl
{
template<typename T>
class Singleton;

template<typename T>
class SingletonHolder
{
public:
 static T& getInstance()
 {
   T* instance = instance_.load(std::memory_order_acquire);
   if (!instance) {
     std::lock_guard<std::mutex> lock(mutex_);
     instance = instance_.load(std::memory_order_relaxed);
     if (!instance) {
        instance = new T();
        instance_.store(instance, std::memory_order_release);
     }
    }
   return *instance;
 }

 static T* get_optional() {
   return instance_.load(std::memory_order_acquire);
 }
private:
  friend class Singleton<T>;
  static std::atomic<T*> instance_;
  static std::mutex mutex_;
};

template <typename T>
std::atomic<T*> SingletonHolder<T>::instance_{nullptr};

template <typename T>
std::mutex SingletonHolder<T>::mutex_;

template<typename T>
class Singleton
{
public:
  static T& getInstance() { return SingletonHolder<T>::getInstance(); }
  static T* try_getInstance() { return SingletonHolder<T>::get_optional(); }
  Singleton(const Singleton& ) = delete;
  Singleton& operator=(const Singleton& ) = delete;
private:
  Singleton() = default;
};

}